package com.ip.db.config;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.parser.ISqlParser;
import com.ip.common.utils.EscapeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

/**
 * @author YUE
 * @create 2020-09-14 10:56
 */
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class}))
@Slf4j
public class MybatisPlusSelectInterceptor implements Interceptor {

    private ISqlParser sqlParser;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
//        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//        MetaObject metaObject = MetaObject
//                .forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
//                        new DefaultReflectorFactory());
//
//        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
//        String sqlCommandType = mappedStatement.getSqlCommandType().toString();////sql语句类型 select、delete、insert、update
//        BoundSql boundSql = statementHandler.getBoundSql();
//        Configuration configuration = mappedStatement.getConfiguration();
//        String s = "";
//        if("SELECT".equals(sqlCommandType)){
//            String originalSql = boundSql.getSql();
//            if(!originalSql.contains("LIKE")){
//                return invocation.proceed();
//            }
//            String regEx = "LIKE [?]";
//            Pattern p = Pattern.compile(regEx);
//            Matcher m = p.matcher(originalSql);
//            s = m.replaceAll("LIKE concat(?)");
//            metaObject.setValue("delegate.boundSql.sql", s);
//        }
//        Object returnVal = invocation.proceed();
//
//        //获取sql语句
//        //String sql = getSql(configuration, boundSql);
////        log.info("Mybatis 拦截器获取SQL:{}",sql);
//        return returnVal;

        // 拦截sql
        Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement)args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        String sql = boundSql.getSql();
        // 处理特殊字符
        modifyLikeSql(sql, parameterObject, boundSql);
        // 返回
        return invocation.proceed();

    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    @SuppressWarnings("unchecked")
    public static String modifyLikeSql(String sql, Object parameterObject, BoundSql boundSql) {
        if (parameterObject instanceof HashMap) {
        } else {
            return sql;
        }
        if (!sql.toLowerCase().contains(" like ") || !sql.toLowerCase().contains("?")) {
            return sql;
        }
        // 获取关键字的个数（去重）
        String[] strList = sql.split("\\?");
        Set<String> keyNames = new HashSet<>();
        for (int i = 0; i < strList.length; i++) {
            if (strList[i].toLowerCase().contains(" like ")) {
                String keyName = boundSql.getParameterMappings().get(i).getProperty();
                keyNames.add(keyName);
            }
        }
        // 对关键字进行特殊字符“清洗”，如果有特殊字符的，在特殊字符前添加转义字符（\）
        for (String keyName : keyNames) {
            HashMap parameter = (HashMap)parameterObject;
            if (keyName.contains("ew.paramNameValuePairs.") && sql.toLowerCase().contains(" like ?")) {
                // 第一种情况：在业务层进行条件构造产生的模糊查询关键字
                Wrapper wrapper = (EntityWrapper)parameter.get("ew");
                parameter = (HashMap)wrapper.getParamNameValuePairs();

                String[] keyList = keyName.split("\\.");
                // ew.paramNameValuePairs.MPGENVAL1，截取字符串之后，获取第三个，即为参数名
                Object a = parameter.get(keyList[2]);
                if (a instanceof String && (a.toString().contains("_") || a.toString().contains("\\") || a.toString()
                        .contains("%"))) {
                    parameter.put(keyList[2],
                            "%" + EscapeUtil.escapeChar(a.toString().substring(1, a.toString().length() - 1)) + "%");
                }
            } else if (!keyName.contains("ew.paramNameValuePairs.") && sql.toLowerCase().contains(" like ?")) {
                // 第二种情况：未使用条件构造器，但是在service层进行了查询关键字与模糊查询符`%`手动拼接
                Object a = parameter.get(keyName);
                if (a instanceof String && (a.toString().contains("_") || a.toString().contains("\\") || a.toString()
                        .contains("%"))) {
                    parameter.put(keyName,
                            "%" + EscapeUtil.escapeChar(a.toString().substring(1, a.toString().length() - 1)) + "%");
                }
            } else {
                // 第三种情况：在Mapper类的注解SQL中进行了模糊查询的拼接
                if(keyName.contains(".")){
                    String[] keyNameArray = keyName.split("\\.");
                    String asFieldKey = keyNameArray[0];
                    String fieldKey = keyNameArray[keyNameArray.length - 1];
                    Object obj = parameter.get(asFieldKey);
                    if(obj != null){
                        Field[] fields = obj.getClass().getDeclaredFields();
                        for (Field field : fields) {
                            if(field.getName().equals(fieldKey)){
                                try{
                                    field.setAccessible(true);
                                    Object fieldValue = field.get(obj);
                                    if (isDoEscapeChar(fieldValue)) {
                                        field.set(obj, EscapeUtil.escapeChar(fieldValue.toString()));
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }

                            }
                        }
                    }
                } else {
                    Object fieldValue = parameter.get(keyName);
                    if (isDoEscapeChar(fieldValue)) {
                        parameter.put(keyName, EscapeUtil.escapeChar(fieldValue.toString()));
                    }
                }

            }
        }
        return sql;
    }

    private static boolean isDoEscapeChar(Object obj){
        return obj instanceof String && (obj.toString().contains("_") || obj.toString().contains("\\") || obj.toString().contains("%"));
    }

}
